-- Import modules --
property lingoF

-------------------------
-- In all these functions the 'list' param can be a of type: ImmutableList, LinearList, PropertyList, Point, Vector, Rect or String.
-- Plus, it can be any object 'a'  from type 'mList' that implements the following methods

-- mList :: int -> b   (could be implemented via getAt)
-- mList.count :: integer

-- mList.getCopy :: nList
-- mList.getRawCopy :: nList
-- mList.getEmpty :: nList

-- mList.add :: b -> void
-- mList.addAt :: integer -> b -> void
-- mList.setAt :: integer -> b -> void
-------------------------



on _string_to_list me,lst
  nl = []
  repeat with i = 1 to lst.char.count()
    nl.add( lst.char[i] )
  end repeat
  return nl
end

on _list_to_string me,lst
  nl = ""
  repeat with i = 1 to lst.count()
    nl = nl & lst[i]
  end repeat
  return nl
end

-- returns a list with the same structure and number of elemets but with any values.
on _list_rawCopy modelList
  if listP(modelList) then return modelList.duplicate()
  if vectorP(modelList) then return modelList.duplicate()
  if objectP(modelList) then return modelList.getRawCopy()
  return modelList
end

on _list_get_empty lst
  case lst.ilk of
    #list,#point,#vector,#rect : return []
    #proplist : return [:]
    #string : return ""
    #immutablelist : return lst.script.new()
  end case
  return lst.getEmpty()
end

-- Used to create a copy of the list to be modified using duplicate().
-- It's faster than copy, but copy creates an exact copy of the list while duplicate creates copy of internal elements as well.

on _list_copy lst
  if listP(lst) then
    nl = _list_rawCopy(lst)
    repeat with i = 1 to lst.count()
      nl[i] =  lst[i]
    end repeat
    return nl
  end if
  if objectP(lst) then return lst.getCopy()
  return lst
end
-------------------------------------


-- Iter
-- Iterates through a list (lst) and applies a non-pure function (fn) to each element.
on public_Iter_2 --> Iter :: (a->void) -> [a] -> void
on _iter me, action,lst
  if stringP(lst) then lst = _list_to_string(me, lst )
  repeat with i = 1 to me._list_length(lst)
    action.$[ lst [i] ]
  end repeat
end



---------------------------------------------------------------------------------
--- Standard Haskell functions.
--- See http://www.haskell.org/ghc/docs/latest/html/libraries/base/Data-List.html
-- (*) means: not working on ImmutableLists
---------------------------------------------------------------------------------


on public_length_1
on _list_length me,lst
  case lst.ilk of
    #string : return lst.length
    #point : return 2
    #vector : return 3
    #rect : return 4
  end case
  return lst.count()
end

on public_Take_2
on _take me, n,lst
  case lst.ilk of
    #string : return char 1 to n of lst
    #immutablelist :
      if n<1 then return lst.getEmpty()
      if lst.count = 0 then return lst.getEmpty()
      return me._cons(lst.head,me._take(n-1,lst.tail))
      
    otherwise:      
      nl = _list_get_empty(lst)
      repeat with i = 1 to min(n,lst.count())
        nl.add(lst[i])
      end repeat
      return nl
  end case
end

on public_Drop_2
on _drop me, n,lst
  case lst.ilk of
    #string : return char 1+n to lst.length of lst
    #immutablelist : 
      if n<1 then return lst
      if lst.count = 0 then return lst.getEmpty()
      return me._drop( n-1,lst.tail)
      
    otherwise:      
      nl = _list_get_empty(lst)
      repeat with i = 1+n to me._list_length(lst)
        nl.add(lst[i])
      end repeat
      return nl
  end case
end


on public_Cons_2
on _cons me ,e,lst
  case lst.ilk of
    #string : return e & lst
    #ImmutableList : return lst.cons(e)
    #point : return [e,lst[1],lst[2]]
    #vector : return [e,lst[1],lst[2],lst[3]] 
    #rect : return [e,lst[1],lst[2],lst[3],lst[4]] 
  end case
  nl = _list_copy ( lst )
  nl.AddAt(1,e)
  return nl
end

on public_null_1
on _null me, lst
  return lst.count = 0
  -- if lst.ilk = #ImmutableList then return lst.count = 0
  -- return me._list_length(lst) = 0
end

on public_Head_1
on _head me, lst
  if lst.ilk = #string then return char 1 of lst
  if lst.ilk = #ImmutableList then return lst.Head
  return lst[1]
end

on public_Tail_1
on _tail me, lst
  if lst.ilk = #ImmutableList then return lst.Tail
  return _drop(me,1,lst)
end


on public_Map_2
on _map me, fn,lst
  case lst.ilk of
    #string : return me._list_to_string(_map(me,fn,me._string_to_list( lst )))
    #immutableList :
      if lst.count = 0 then return lst
      return me._cons(fn[lst.head], me._map(fn,lst.tail))
    otherwise :
      nl = _list_rawCopy(lst)
      repeat with i = 1 to me._list_length(lst)
        nl[i] =  fn[lst[i]]
      end repeat
      return nl
  end case
end



-- (*)
on public_ZipWith_3
on _ZipWith me, fn,lst1,lst2
  
  if stringP(lst1) then return me._list_to_string(ZipWith(me,fn,me._string_to_list( lst1 ),lst2))
  if stringP(lst2) then lst2 = me._string_to_list( lst2 )
  nl = _list_rawCopy(lst1)
  
  repeat with i = 1 to me._list_length( nl )
    nl[i] = fn[lst1[i]][lst2[i]]
  end repeat 
  return nl
end

-- (*)
on public_SortBy_2
on _SortBy me, comparer,lst
  if stringP(lst) then return me._list_to_string(me._SortBy(comparer,me._string_to_list( lst )))
  
  A = me._list_copy(lst)
  n = me._list_length( A )
  swapped = true
  repeat while swapped
    swapped = false
    repeat with i = 1 to n-1
      if comparer[A[i]][A[i+1]]>0 then
        aux = A[i+1]
        A[i+1] = A[i]
        A[i] = aux
        swapped = true
      end if
    end repeat
    n = n - 1
  end repeat
  return A
end


on public_Filter_2
on _Filter me, predicate,lst
  case lst.ilk of
    #string : return me._list_to_string(me.filter(predicate,me._string_to_list( lst )))
    #immutableList : 
      if lst.count = 0 then return lst
      if predicate[lst.head] then return me._cons(lst.head, me._filter(predicate, lst.tail))
      return me._filter(predicate, lst.tail)
    otherwise :
      nl = _list_get_empty(lst)
      repeat with i = 1 to me._list_length(lst)
        if predicate[lst[i]] then nl.add(lst[i])
      end repeat
      return nl
  end case
end

on public_Foldl_3
on _fold_left me, combinatorFN,initialValue,lst
  if stringP(lst) then return _fold_left(me,combinatorFN,initialValue , me._string_to_list(lst))
  repeat with i = 1 to me._list_length(lst)
    initialValue = combinatorFN[initialValue][lst[i]]
  end repeat
  return initialValue
end

on public_Foldr_3
on _fold_right me, combinatorFN,initialValue,lst
  if stringP(lst) then return _fold_right(me,combinatorFN,initialValue,me._string_to_list( lst ))
  repeat with i = me._list_length(lst) down to 1
    initialValue = combinatorFN[lst[i]][initialValue]
  end repeat
  return initialValue
end




on public_append_2
on _append me, lst1,lst2
  if stringP(lst1) then
    if stringP(lst2) then return lst1 & lst2
    return lst1 & me._fold_right(me.cons,"",lst2)
  end if
  return me._fold_right(me.cons,lst2,lst1)
end

on public_intersperse_2
on _intersperse me, element,lst
  if stringP(lst) then return me._list_to_string(_intersperse(me,element,me._string_to_list( lst )))
  c = me._list_length(lst)
  nl = _list_get_empty(lst)
  if c=0 then return nl
  nl.add(  lst[1] )
  repeat with i = 2 to c
    nl.add(element)
    nl.add(lst[i])
  end repeat
  return nl
end

on public_intercalate_2
on _intercalate me, element, lst
  if _null(me,lst) then return lst
  x = var()
  return me._fold_left(LingoF().fun(x)[ me.append[me.append[x][element]]] , _head(me,lst), _tail(me,lst) )
end

on public_reverse_1
on _reverse me, lst
  return me._fold_left(me.LingoF.flip[me.cons],  _list_get_empty(lst) , lst )
end